Repository navigation
perf(agc): remember an evicting import request's budget refusal until it could succeed - #6730
Conversation
At a full host import budget with nothing evictable, a request that may evict tried again on every use, and each try walked every live import to add up what could be evicted. In a title's first scene at its 6400 MiB budget that was 37,627 refusals per present, each one a walk over about 4,400 imports, while the address-based builds kept every import held. Such a request now remembers its final refusal, the one with no smaller block left to try. Later requests for the same run, from the same range, are answered from that memory, still counted as refused and remembered, until an import is made or retired, the budget or the import window changes, the run's ranges change, or an import could have become evictable. An import becomes evictable only once the use count passes its last use by the 256-use protection, and a held one also needs a higher build floor, so the walk records the first such use among the imports it could not evict, held ones apart: the memory survives a new build while no held import can age out yet. Requests that may not evict keep their behaviour.
The remembered refusal of a request that may evict expired on any budget change. A smaller budget can only keep the request refused, and boykopovar#6479 moves the budget in 64 MiB steps once a second, so the memory now ends only when the budget grows, as boykopovar#6479's stillRefused does.
An evicting refusal was remembered for the range holding the requested address and dropped by a request for another range of the same run, so requests landing in different ranges of one refused block, as uploads and accesses across a block of adjacent ranges do, scanned the imports every time. The memory now keeps the room the refusal left and answers any range of the run that is larger than it: such a range cannot step down to a block of its own either. A smaller range still recomputes, and a run that cannot step down at all is answered for every range.
A join's budget refusal was never remembered, so a buffer bound across ranges that the budget could not take tried the join, with a scan of every import, at each upload's pre-pass. A join now remembers its final refusal like any other evicting request, under the same conditions. The memory keeps whether it was a join and answers only the same kind of request, since a join skips the held-import check and never steps down.
An evicting request whose block is refused but leaves room for its own range steps down to a smaller block, and only the last block it tried was remembered. Once an address-based build had stepped down inside a block, every later evicting request for the rest of that block scanned every import at the block's level before it reached the memory below. A refusal is now remembered before the request steps down, with the room it left. A remembered refusal answers with the same decision the scan would make: while the memory is current only a lower budget can change that room, so the request steps down to the same smaller block when its range fits in the room, and is refused otherwise.
|
Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.
Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.
|
…mber-evicting-refusals
|
Data point from a CPU-bound scene on Windows: RTX 3060 12 GB (NVIDIA 617.42), i5-12400F, Windows 10 19045, MinGW GCC Release. Minecraft Dungeons II (PPSA16064 01.100.100), title screen, no input, 200 s rows,
Within the run-to-run spread (about ±1.5 % here), no regression, 0 fatal. The title screen on a 12 GB card has no evicting imports to refuse, so it doesn't exercise the path this changes. AI-assisted: yes (Claude Code) |
…mber-evicting-refusals
|
AMD / Windows run of this PR at 7539ba6: Radeon 780M (RDNA 3, 0x15bf), AMD driver 25.10.20.01 (32.0.21020.1007), Windows 11 Home 10.0.26100, WinLibs GCC 15.2.0 r7, Release. Every ctest entry built from the test targets this PR changes passes (63):
AI-assisted: yes (runs made on the device above). |
…mber-evicting-refusals boykopovar#6457's branch now carries main's per-device import settles. Its ImportSettle sits where this branch adds EvictingRefusal; keep both.
|
This pull request is labeled |
Resolves the AGC driver debt file bullet by bullet.
Main's sameRange for a failed import now takes the import budget, as the HostImport overload does, so a recorded failure is compared against the run the budget would import again.
The host-import debt entry takes boykopovar#6472's budget sentence and keeps this branch's sentence on remembered refusals.
# Conflicts: # core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp # docs/dev/debt/libSceAgcDriver.md
# Conflicts: # docs/dev/debt/libSceAgcDriver.md
# Conflicts: # core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
# Conflicts: # core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp # core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp # docs/dev/debt/libSceAgcDriver.md
What
At a full host import budget, a request that may evict asked again on every use. That covers an upload's region that needs an import, and
HostImportFor. Each time it walked every live import inevictableBytesand was refused again. #6457 remembers the refusals of requests that may not evict. This PR remembers the evicting ones too, for exactly as long as the refusal can't turn into a success.Depends on: #6457. This branch is #6457 plus this PR's commits and a merge of
main5d9dcc4.Why. In Resident Evil Village (PPSA01556)'s first scene the 6400 MiB budget is full, and one upload range is refused at every use:
How. Without a held overlapping import, an evicting request is refused when the bytes of non-evictable imports outside its run, plus the run's own bytes, exceed the budget. A refusal can only become a success when one of these happens:
So each refusal is remembered with the state that decided it and the room it left, for every block a step-down tries. It is answered from memory, and still counted as refused and remembered, while all of these hold:
epochandliveBytesare unchanged: no import was made or retired;stillRefusedrule;A remembered refusal reproduces the scan's own decision: if the run can step down and the request's range fits in the room left, less any budget drop since, it steps down as the scan would, without a new walk; otherwise it is refused. A join's refusal is remembered too, kept apart from other requests for the same run, since a join skips the held-import check and never steps down.
Every condition is exact or errs toward trying again, so a request that could succeed is never answered from memory.
Not changed:
refreshImportsdrops stale entries, and a device change andClearHostImportsclear them.Cost in Resident Evil Village's scene (by design, not measured yet): at ~72 address-based builds per second, which restamp the imports they map, the memory is walked again at most once per build, instead of once per request.
Overlap. At this head the overlap check lists the same other PRs as for #6457, so this PR adds no conflict beyond #6457's own. #6479, also stacked on #6457, changes the same budget path in
importRunAtand adds tests toBdaResources.cpp. This PR uses #6479's rule for a budget that moves, so whichever lands second keeps both. #6918 (Scramblrrrr, draft) clips a partly committed import window to the aligned committed span that holds the request, inwindowRun, and merges cleanly with this PR. The evicting refusals this PR remembers are keyed by the runwindowRunreturns and compared by its ranges, so a clipped window is remembered as its own run, and a window whose committed span changes is a new request.Tested
agc_driver_graphics' mock device):GuestBufferMemory.cppwith the tests kept:8 evicting requests for one refused piece at a full budget with nothing evictable scanned the imports 8 times, not once. Green:Graphics validation tests passed.ctest -R '^agc'×5: 605/605 three times, and twice 604/605. The one failure wasagc_driver_preparation_scope_settle, a known load flake that passes 10/10 alone.agc_driver_graphicsincluded):guest_kqueuefails, on both sides; it fails on that runner withmaintoo.GuestBufferMemory.cpp:8 evicting requests alternating between two 64 KiB ranges of one refused 256 KiB block scanned the imports 8 times, not once;8 uploads bound across three refused ranges scanned the imports for their join 8 times, not once;8 evicting requests that step down from a refused block scanned the imports 8 times, not once.agc_driver_graphics×10: 10/10;ctest -R '^agc': 607/607 (43 skipped).GuestBufferMemory.cppand the new tests,agc_driver_graphicsfails in every one of 3 runs on Linux and Windows; with the three commits it passes 10 of 10 on both. In the suites onlyguest_kqueuefails on Windows, on both sides.main:ctest -R '^agc'on GitHub's Linux runner at the merged head passes 606 of 607; the one failure is the sameagc_driver_preparation_scope_settleload flake.-fsyntax-only -Wall: no new diagnostics against perf(agc): import adjacent host ranges as aligned blocks and stop re-trying budget refusals #6457's head.main52798f7 (comment): all 63 tests built from the targets this PR changes pass.python3 tools/check_conventions.py --base origin/main: passed. No comment lines added.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)